#include "stream_reassembler.hh"
// Dummy implementation of a stream reassembler.

// For Lab 1, please replace with a real implementation that passes the
// automated checks run by `make check_lab1`.

// You will need to add private members to the class declaration in
// `stream_reassembler.hh`

template <typename... Targs>
void DUMMY_CODE(Targs &&.../* unused */) {}

using namespace std;

StreamReassembler::StreamReassembler(const size_t capacity)
    : _output(capacity)
    , _capacity(capacity)
    , sw_asm_nxt_idx(0)
    , eof_idx(0)
    , eof_got(false)
    , unassemble_part({})
    , unassemble_bytes(0) {}
StreamReassembler::~StreamReassembler() = default;
//! \details This function accepts a substring (aka a segment) of bytes,
//! possibly out-of-order, from the logical stream, and assembles any newly
//! contiguous substrings and writes them into the output stream in order.
void StreamReassembler::push_substring(const string &data, const size_t index, const bool eof) {
    size_t sw_idx = _output.bytes_read();
    if (eof) {
        eof_idx = index + data.size();
        eof_got = true;
    }
    // Case 1 : Can read more into stream
    size_t maxc_idx = sw_idx + _capacity;
    if (index <= sw_asm_nxt_idx) {
        size_t ideal_idx = index + data.size();
        if (ideal_idx >= sw_asm_nxt_idx) {
            size_t final_idx = std::min(ideal_idx, maxc_idx);
            final_idx = eof_got ? std::min(eof_idx, final_idx) : final_idx;

            // try to assemble unassemble data into stream
            std::string wdata = data.substr(sw_asm_nxt_idx - index, final_idx - sw_asm_nxt_idx);
            std::vector<size_t> commit_unassemble_part(0);
            for (const auto &[uidx, ud] : unassemble_part) {
                // judge whether it has intersection
                size_t max_idx, max_end;
                size_t min_end;

                max_idx = std::max(uidx, sw_asm_nxt_idx);
                max_end = std::max(uidx + ud.size(), sw_asm_nxt_idx + wdata.size());

                min_end = std::min(uidx + ud.size(), sw_asm_nxt_idx + wdata.size());

                if ((max_idx <= min_end)) {
                    if (max_idx == uidx && min_end == (uidx + ud.size())) {
                        commit_unassemble_part.emplace_back(uidx);
                        unassemble_bytes -= ud.size();
                    } else {
                        wdata += ud.substr(min_end - max_idx, max_end - min_end);
                        final_idx = max_end;

                        commit_unassemble_part.emplace_back(uidx);
                        unassemble_bytes -= ud.size();
                    }
                }
            }
            for (const size_t &idx : commit_unassemble_part) {
                unassemble_part.erase(idx);
            }
            _output.write(wdata);
            sw_asm_nxt_idx = final_idx;
            if (eof_got && sw_asm_nxt_idx == eof_idx) {
                _output.end_input();
            }
        }
    }
    // Case 2 : Out of order reached
    else if (index < maxc_idx) {
        size_t ideal_idx = index + data.size();
        size_t final_idx = std::min(ideal_idx, maxc_idx);
        final_idx = eof_got ? std::min(eof_idx, final_idx) : final_idx;
        if (index < final_idx) {
            std::string wdata = data.substr(0, final_idx - index);

            std::vector<size_t> merged_unassemble_part(0);
            size_t commit_idx = index;
            for (const auto &[uidx, ud] : unassemble_part) {
                size_t max_idx;
                max_idx = std::max(uidx, commit_idx);

                size_t min_idx, min_end;
                min_idx = std::min(uidx, commit_idx);
                min_end = std::min(uidx + ud.size(), commit_idx + wdata.size());

                const std::string &ahead = min_idx == commit_idx ? wdata : ud;
                const std::string &later = max_idx == uidx && ahead != ud ? ud : wdata;

                bool whole_intersection = max_idx == commit_idx && min_end == (commit_idx + wdata.size());
                whole_intersection = whole_intersection || (max_idx == uidx && min_end == (uidx + ud.size()));
                if ((max_idx <= min_end)) {
                    if (whole_intersection) {
                        commit_idx = min_idx;
                        wdata = wdata.size() > ud.size() ? wdata : ud;

                        merged_unassemble_part.emplace_back(uidx);
                        unassemble_bytes -= ud.size();
                    } else {
                        wdata = ahead.substr(0, max_idx - min_idx) + later;
                        commit_idx = min_idx;

                        merged_unassemble_part.emplace_back(uidx);
                        unassemble_bytes -= ud.size();
                    }
                }
            }
            for (const size_t &midx : merged_unassemble_part) {
                unassemble_part.erase(midx);
            }
            unassemble_part.insert_or_assign(commit_idx, wdata);
            unassemble_bytes += wdata.size();
        }
    }

    return;
}

size_t StreamReassembler::unassembled_bytes() const { return unassemble_bytes; }

bool StreamReassembler::empty() const { return unassemble_bytes == 0; }

size_t StreamReassembler::next_to_assemble() const { return sw_asm_nxt_idx; }
